package com.lalilu.lmedia.repository.impl

import com.lalilu.lmedia.database.PlaylistDao
import com.lalilu.lmedia.database.SongInPlaylistDao
import com.lalilu.lmedia.database.link
import com.lalilu.lmedia.entity.LPlaylist
import com.lalilu.lmedia.entity.LSong
import com.lalilu.lmedia.entity.SongInPlaylist
import com.lalilu.lmedia.repository.FavoriteRepository
import com.lalilu.lmedia.repository.FavoriteRepository.Companion.FAVORITE_PLAYLIST_ID
import com.lalilu.lmedia.repository.PlaylistRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart

@OptIn(ExperimentalCoroutinesApi::class)
class PlaylistRepositoryImpl(
    private val playlistDao: PlaylistDao,
    private val songInPlaylistDao: SongInPlaylistDao,
    private val getSongOrNull: (String) -> LSong?
) : PlaylistRepository, FavoriteRepository {
    override fun getAllPlaylistFlow(): Flow<List<LPlaylist>> = playlistDao.getAllFlow()

    override fun getAllPlaylistWithDetailFlow(): Flow<List<LPlaylist>> {
        return getAllPlaylistFlow()
            .onStart { makeSureFavoritePlaylistExist() }
            .flowOn(Dispatchers.IO)
            .combine(playlistDao.getAllDetailByIdFlow()) { list, map ->
                list.onEach {
                    it.songs = map[it._id]?.mapNotNull(getSongOrNull) ?: emptyList()
                }
            }
    }

    override fun isSongContainInPlaylist(mediaId: String, pId: Long): Flow<Boolean> {
        return songInPlaylistDao.isContainItemInPlaylist(mediaId, pId)
            .distinctUntilChanged()
            .mapLatest { result -> result ?: false }
    }

    override fun getSongInPlaylistsFlowById(pId: Long): Flow<List<SongInPlaylist>> {
        return songInPlaylistDao.getSongInPlaylistsByPlaylistId(pId)
    }

    override fun getSongInPlaylistsById(pId: Long): List<SongInPlaylist> {
        return songInPlaylistDao.getSongInPlaylistsById(pId)
    }

    override fun getPlaylistById(pId: Long): Flow<LPlaylist> {
        return playlistDao.getFlowById(pId)
            .distinctUntilChanged()
            .onStart { makeSureFavoritePlaylistExist() }
            .flowOn(Dispatchers.IO)
    }

    override fun savePlaylist(playlist: LPlaylist) {
        playlistDao.insertToEnd(playlist)
    }

    override fun deletePlaylist(playlist: LPlaylist) {
        if (playlist._id != FAVORITE_PLAYLIST_ID) {
            playlistDao.removeItem(playlist)
        }
    }

    override fun deletePlaylists(playlists: List<LPlaylist>) {
        playlistDao.removeItems(playlists.filterNot { it._id == FAVORITE_PLAYLIST_ID })
    }

    override fun saveSongIntoPlaylist(pId: Long, mediaId: String) {
        songInPlaylistDao.insertToEnd(SongInPlaylist(playlistId = pId, mediaId = mediaId))
    }

    override fun saveSongsIntoPlaylists(pIds: List<Long>, mediaIds: List<String>) {
        songInPlaylistDao.insertToEnd(pIds.map { pId ->
            mediaIds.map { mediaId ->
                SongInPlaylist(playlistId = pId, mediaId = mediaId)
            }
        }.flatten())
    }

    override fun deleteSongFromPlaylist(pId: Long, mediaId: String) {
        songInPlaylistDao.getSongInPlaylist(pId, mediaId)?.let {
            songInPlaylistDao.removeItem(it)
        }
    }

    override fun deleteSongsFromPlaylist(songs: List<LSong>, playlist: LPlaylist) {
        songInPlaylistDao.deleteSongInPlaylists(songs.map {
            SongInPlaylist(
                playlistId = playlist._id,
                mediaId = it.id
            )
        })
    }

    override fun reorderPlaylist(orderedList: List<LPlaylist>) {
        playlistDao.updateAll(orderedList.link())
    }

    override fun reorderSongInPlaylist(orderedList: List<SongInPlaylist>) {
        songInPlaylistDao.updateAll(orderedList.link())
    }

    override fun movePlaylist(
        playlist: LPlaylist,
        playlistTarget: LPlaylist,
        beforeTarget: Boolean
    ) {
        playlistDao.moveItem(playlist, playlistTarget, beforeTarget)
    }

    override fun moveSongInPlaylist(
        item: SongInPlaylist,
        target: SongInPlaylist,
        beforeTarget: Boolean
    ) {
        songInPlaylistDao.moveItem(item, target, beforeTarget)
    }

    override suspend fun addToFavorite(song: LSong) {
        addToFavorite(song.id)
    }

    override suspend fun addToFavorite(mediaId: String) {
        makeSureFavoritePlaylistExist()

        songInPlaylistDao.insertToEnd(
            SongInPlaylist(playlistId = FAVORITE_PLAYLIST_ID, mediaId = mediaId)
        )
    }

    override suspend fun removeFromFavorite(song: LSong) {
        removeFromFavorite(song.id)
    }

    override suspend fun removeFromFavorite(mediaId: String) {
        makeSureFavoritePlaylistExist()

        songInPlaylistDao.getSongInPlaylist(FAVORITE_PLAYLIST_ID, mediaId)?.let {
            songInPlaylistDao.removeItem(it)
        }
    }

    override fun checkIsFavorite(mediaId: String): Flow<Boolean> {
        return songInPlaylistDao.isContainItemInPlaylist(mediaId, FAVORITE_PLAYLIST_ID)
            .mapLatest { it ?: false }
    }

    override suspend fun makeSureFavoritePlaylistExist(pId: Long) {
        if (pId == FAVORITE_PLAYLIST_ID && playlistDao.getById(FAVORITE_PLAYLIST_ID) == null) {
            playlistDao.insertToEnd(LPlaylist(_id = FAVORITE_PLAYLIST_ID, _title = "我喜欢"))
        }
    }
}